package com.jakewharton.rxbinding2.component;

import ohos.agp.components.*;
import com.jakewharton.rxbinding2.InitialValueObservable;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
import ohos.multimodalinput.event.KeyEvent;
import ohos.multimodalinput.event.TouchEvent;

import java.util.concurrent.Callable;

import static com.jakewharton.rxbinding2.internal.Functions.CALLABLE_ALWAYS_TRUE;
import static com.jakewharton.rxbinding2.internal.Functions.PREDICATE_ALWAYS_TRUE;
import static com.jakewharton.rxbinding2.internal.Preconditions.checkNotNull;

/**
 * Static factory methods for creating {@linkplain Observable observables} and {@linkplain Consumer
 * actions} for {@link Component}.
 */
public final class RxComponent {
  /**
   * Create an observable which emits on {@code component} attach events. The emitted value is
   * unspecified and should only be used as notification.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Observable<Object> attaches(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentAttachesObservable(component, true);
  }

  /**
   * Create an observable which emits on {@code component} detach events. The emitted value is
   * unspecified and should only be used as notification.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Observable<Object> detaches(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentAttachesObservable(component, false);
  }

  /**
   * Create an observable of attach and detach events on {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Observable<ComponentAttachEvent> attachEvents(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentAttachEventObservable(component);
  }

  /**
   * Create an observable which emits on {@code component} click events. The emitted value is
   * unspecified and should only be used as notification.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link Component#setClickedListener} to observe
   * clicks. Only one observable can be used for a component at a time.
   */
  public static Observable<Object> clicks(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentClickObservable(component);
  }

  /**
   * Create an observable of {@link DragEvent} for drags on {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link Component#setDraggedListener} to observe
   * drags. Only one observable can be used for a component at a time.
   */
  public static Observable<DragInfo> drags(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentDragObservable(component, PREDICATE_ALWAYS_TRUE);
  }

  /**
   * Create an observable of {@link DragEvent} for {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link Component#setDraggedListener} to observe
   * drags. Only one observable can be used for a component at a time.
   *
   * @param handled Predicate invoked with each value to determine the return value of the
   * underlying {@link Component.DraggedListener}.
   */
  public static Observable<DragInfo> drags(Component component,
      Predicate<? super DragInfo> handled) {
    checkNotNull(component, "component == null");
    checkNotNull(handled, "handled == null");
    return new ComponentDragObservable(component, handled);
  }



  /**
   * Create an observable of booleans representing the focus of {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link Component#setFocusChangedListener} to observe
   * focus change. Only one observable can be used for a component at a time.
   * <p>
   * <em>Note:</em> A value will be emitted immediately on subscribe.
   */
  public static InitialValueObservable<Boolean> focusChanges(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentFocusChangeObservable(component);
  }

  /**
   * Create an observable which emits on {@code component} globalLayout events. The emitted value is
   * unspecified and should only be used as notification.
   * <p></p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link
   * ComponentTreeObserver#addTreeLayoutChangedListener} to observe global layouts. Multiple observables
   * can be used for a component at a time.
   */
  public static Observable<Object> globalLayouts(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentTreeObserverGlobalLayoutObservable(component);
  }

  /**
   * Create an observable which emits on {@code component} long-click events. The emitted value is
   * unspecified and should only be used as notification.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link Component#setLongClickedListener} to observe
   * long clicks. Only one observable can be used for a component at a time.
   */
  public static Observable<Object> longClicks(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentLongClickObservable(component, CALLABLE_ALWAYS_TRUE);
  }

  /**
   * Create an observable which emits on {@code component} long-click events. The emitted value is
   * unspecified and should only be used as notification.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link Component#setLongClickedListener} to observe
   * long clicks. Only one observable can be used for a component at a time.
   *
   * @param handled Predicate invoked each occurrence to determine the return value of the
   * underlying {@link Component.LongClickedListener}.
   */
  public static Observable<Object> longClicks(Component component,
      Callable<Boolean> handled) {
    checkNotNull(component, "component == null");
    checkNotNull(handled, "handled == null");
    return new ComponentLongClickObservable(component, handled);
  }



  /**
   * Create an observable of scroll-change events for {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Observable<ComponentScrollChangeEvent> scrollChangeEvents(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentScrollChangeEventObservable(component);
  }


  /**
   * Create an observable of touch events for {@code component}.
   * <p>
   * <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
   * object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
   * on a different thread). If you want to cache or delay reading the items emitted then you must
   * map values through a function which calls {@link TouchEvent#obtain(TouchEvent)} or
   * {@link TouchEvent#obtainNoHistory(TouchEvent)} to create a copy.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link Component#setTouchEventListener} to observe
   * touches. Only one observable can be used for a component at a time.
   */
  public static Observable<TouchEvent> touches(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentTouchObservable(component, PREDICATE_ALWAYS_TRUE);
  }

  /**
   * Create an observable of touch events for {@code component}.
   * <p>
   * <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
   * object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
   * on a different thread). If you want to cache or delay reading the items emitted then you must
   * map values through a function which calls {@link TouchEvent#obtain(TouchEvent)} or
   * {@link TouchEvent#obtainNoHistory(TouchEvent)} to create a copy.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <p>
   * <em>Warning:</em> The created observable uses {@link Component#setTouchEventListener} Listener} to observe
   * touches. Only one observable can be used for a component at a time.
   *
   * @param handled Predicate invoked with each value to determine the return value of the
   * underlying {@link Component.TouchEventListener}.
   */
  public static Observable<TouchEvent> touches(Component component,
      Predicate<? super TouchEvent> handled) {
    checkNotNull(component, "component == null");
    checkNotNull(handled, "handled == null");
    return new ComponentTouchObservable(component, handled);
  }

  /**
   * Create an observable of key events for {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <em>Warning:</em> The created observable uses {@link Component#setKeyEventListener} to observe
   * key events. Only one observable can be used for a component at a time.
   */
  public static Observable<KeyEvent> keys(Component component) {
    checkNotNull(component, "component == null");
    return new ComponentKeyObservable(component, PREDICATE_ALWAYS_TRUE);
  }

  /**
   * Create an observable of key events for {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   * <em>Warning:</em> The created observable uses {@link Component#setKeyEventListener} to observe
   * key events. Only one observable can be used for a component at a time.
   *
   * @param handled Predicate invoked each occurrence to determine the return value of the
   * underlying {@link Component.KeyEventListener}.
   */
  public static Observable<KeyEvent> keys(Component component,
      Predicate<? super KeyEvent> handled) {
    checkNotNull(component, "component == null");
    checkNotNull(handled, "handled == null");
    return new ComponentKeyObservable(component, handled);
  }


  /**
   * An action which sets the clickable property of {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Consumer<? super Boolean> clickable(final Component component) {
    checkNotNull(component, "component == null");
    return new Consumer<Boolean>() {
      @Override public void accept(Boolean value) {
        component.setClickable(value);
      }
    };
  }

  /**
   * An action which sets the enabled property of {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Consumer<? super Boolean> enabled(final Component component) {
    checkNotNull(component, "component == null");
    return new Consumer<Boolean>() {
      @Override public void accept(Boolean value) {
        component.setEnabled(value);
      }
    };
  }

  /**
   * An action which sets the pressed property of {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Consumer<? super Boolean> pressed(final Component component) {
    checkNotNull(component, "component == null");
    return new Consumer<Boolean>() {
      @Override public void accept(Boolean value) {
        component.setPressState(value);
      }
    };
  }

  /**
   * An action which sets the selected property of {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Consumer<? super Boolean> selected(final Component component) {
    checkNotNull(component, "component == null");
    return new Consumer<Boolean>() {
      @Override public void accept(Boolean value) {
        component.setSelected(value);
      }
    };
  }

  /**
   * An action which sets the visibility property of {@code component}. {@code false} values use
   * {@code Component.GONE}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   */
  public static Consumer<? super Boolean> visibility(Component component) {
    checkNotNull(component, "component == null");
    return visibility(component, Component.HIDE);
  }

  /**
   * An action which sets the visibility property of {@code component}.
   * <p>
   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
   * to free this reference.
   *
   * @param visibilityWhenFalse Visibility to set on a {@code false} value ({@code Component.INVISIBLE}
   * or {@code Component.GONE}).
   */
  public static Consumer<? super Boolean> visibility(final Component component,
      final int visibilityWhenFalse) {
    checkNotNull(component, "component == null");
    if (visibilityWhenFalse == Component.VISIBLE) {
      throw new IllegalArgumentException(
          "Setting visibility to VISIBLE when false would have no effect.");
    }
    if (visibilityWhenFalse != Component.INVISIBLE && visibilityWhenFalse != Component.HIDE) {
      throw new IllegalArgumentException("Must set visibility to INVISIBLE or GONE when false.");
    }
    return new Consumer<Boolean>() {
      @Override public void accept(Boolean value) {
        component.setVisibility(value ? Component.VISIBLE : visibilityWhenFalse);
      }
    };
  }

  private RxComponent() {
    throw new AssertionError("No instances.");
  }

//    //TODO　无对应方法
//  /**
//   * Create an observable for draws on {@code component}.
//   * <p>
//   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
//   * to free this reference.
//   * <p>
//   * <em>Warning:</em> The created observable uses {@link ViewTreeObserver#addOnDrawListener} to
//   * observe draws. Multiple observables can be used for a component at a time.
//   */
//  public static Observable<Object> draws(Component component) {
//    checkNotNull(component, "component == null");
//    return new ViewTreeObserverDrawObservable(component);
//  }
//  /**
//   * Create an observable of hover events for {@code component}.
//   * <p>
//   * <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
//   * object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
//   * on a different thread). If you want to cache or delay reading the items emitted then you must
//   * map values through a function which calls {@link TouchEvent#obtain(TouchEvent)} or
//   * {@link TouchEvent#obtainNoHistory(TouchEvent)} to create a copy.
//   * <p>
//   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
//   * to free this reference.
//   * <p>
//   * <em>Warning:</em> The created observable uses {@link View#setOnHoverListener} to observe
//   * touches. Only one observable can be used for a component at a time.
//   */
//  public static Observable<TouchEvent> hovers(Component component) {
//    checkNotNull(component, "component == null");
//    return new ViewHoverObservable(component, PREDICATE_ALWAYS_TRUE);
//  }
//
//  /**
//   * Create an observable of hover events for {@code component}.
//   * <p>
//   * <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
//   * object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
//   * on a different thread). If you want to cache or delay reading the items emitted then you must
//   * map values through a function which calls {@link TouchEvent#obtain(TouchEvent)} or
//   * {@link TouchEvent#obtainNoHistory(TouchEvent)} to create a copy.
//   * <p>
//   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
//   * to free this reference.
//   * <p>
//   * <em>Warning:</em> The created observable uses {@link View#setOnHoverListener} to observe
//   * touches. Only one observable can be used for a component at a time.
//   *
//   * @param handled Predicate invoked with each value to determine the return value of the
//   * underlying {@link View.OnHoverListener}.
//   */
//  public static Observable<TouchEvent> hovers(Component component,
//          Predicate<? super TouchEvent> handled) {
//    checkNotNull(component, "component == null");
//    checkNotNull(handled, "handled == null");
//    return new ViewHoverObservable(component, handled);
//  }
//
//  /**
//   * Create an observable which emits on {@code component} layout changes. The emitted value is
//   * unspecified and should only be used as notification.
//   * <p>
//   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
//   * to free this reference.
//   */
//  public static Observable<Object> layoutChanges(Component component) {
//    checkNotNull(component, "component == null");
//    return new ComponentLayoutChangeObservable(component);
//  }
//
//  /**
//   * Create an observable of layout-change events for {@code component}.
//   * <p>
//   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
//   * to free this reference.
//   */
//  public static Observable<ViewLayoutChangeEvent> layoutChangeEvents(Component component) {
//    checkNotNull(component, "component == null");
//    return new ViewLayoutChangeEventObservable(component);
//  }
//  /**
//   * Create an observable for pre-draws on {@code component}.
//   * <p>
//   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
//   * to free this reference.
//   * <p>
//   * <em>Warning:</em> The created observable uses {@link ViewTreeObserver#addOnPreDrawListener} to
//   * observe pre-draws. Multiple observables can be used for a component at a time.
//   */
//  public static Observable<Object> preDraws(Component component,
//          Callable<Boolean> proceedDrawingPass) {
//    checkNotNull(component, "component == null");
//    checkNotNull(proceedDrawingPass, "proceedDrawingPass == null");
//    return new ViewTreeObserverPreDrawObservable(component, proceedDrawingPass);
//  }
//  /**
//   * Create an observable of integers representing a new system UI visibility for {@code component}.
//   * <p>
//   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
//   * to free this reference.
//   * <p>
//   * <em>Warning:</em> The created observable uses
//   * {@link View#setOnSystemUiVisibilityChangeListener} to observe system UI visibility changes.
//   * Only one observable can be used for a component at a time.
//   */
//  public static Observable<Integer> systemUiVisibilityChanges(Component component) {
//    checkNotNull(component, "component == null");
//    return new ViewSystemUiVisibilityChangeObservable(component);
//  }
//  /**
//   * An action which sets the activated property of {@code component}.
//   * <p>
//   * <em>Warning:</em> The created observable keeps a strong reference to {@code component}. Unsubscribe
//   * to free this reference.
//   */
//  public static Consumer<? super Boolean> activated(final Component component) {
//    checkNotNull(component, "component == null");
//    return new Consumer<Boolean>() {
//      @Override public void accept(Boolean value) {
//        component.setActivated(value);
//      }
//    };
//  }
}
